let marbleRaceColorUtility = {
	hsv100ToRGB1: function (hsv) {
		let h = hsv[0] / 60, s = hsv[1] / 100, v = hsv[2] / 100
		let i = Math.floor(h) % 6
		let f = h - Math.floor(h)
		let p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s)
		switch (i) {
			case 0:
				return [v, t, p]

			case 1:
				return [q, v, p]

			case 2:
				return [p, v, t]

			case 3:
				return [p, q, v]

			case 4:
				return [t, p, v]

			case 5:
				return [v, p, q]

			default:
				console.log(hsv)
				console.error("hsv100ToRGB1: unknown error")
		}
	},
	RGBStrToRGB255: function (rgbstr) {
		const map = {
			'0': 0,
			'1': 1,
			'2': 2,
			'3': 3,
			'4': 4,
			'5': 5,
			'6': 6,
			'7': 7,
			'8': 8,
			'9': 9,
			'A': 10,
			'B': 11,
			'C': 12,
			'D': 13,
			'E': 14,
			'F': 15,
			'a': 10,
			'b': 11,
			'c': 12,
			'd': 13,
			'e': 14,
			'f': 15
		}
		function get(h, l) {
			return (map[rgbstr[h]] << 4) | map[rgbstr[l]]
		}
		return [get(1, 2), get(3, 4), get(5, 6)]
	},
	RGB255ToRGBStr: function (r, g, b) {
		const str = '0123456789ABCDEF'
		return '#' + str[r >> 4] + str[r & 15] + str[g >> 4] + str[g & 15] + str[b >> 4] + str[b & 15]
	},
	RGB1ToRGBStr: function (rgb) {
		let r = Math.floor(rgb[0] * 255)
		let g = Math.floor(rgb[1] * 255)
		let b = Math.floor(rgb[2] * 255)
		return this.RGB255ToRGBStr(r, g, b)
	},
	hsv100ToRGBStr: function (hsv) {
		return this.RGB1ToRGBStr(this.hsv100ToRGB1(hsv))
	},
	colorToRGBStr: function (color) {
		switch (color.mode) {
			case 'RGB255':
				return this.RGB255ToRGBStr(color[0], color[1], color[2])
			default:
				return this.hsv100ToRGBStr(color)
		}
	},
	time: 0,
	calculateColor: function (color, size) {
		let lineWidth = marbleRaceConfig.theme.lineWidth
		let fill = marbleRaceConfig.theme.secondary
		let stroke = undefined
		let sprite = undefined
		let spriteScale = undefined

		if (typeof color == 'string') {
			fill = color
			stroke = fill
		} else if (color instanceof Array) {
			fill = marbleRaceColorUtility.colorToRGBStr(color)
			stroke = fill
		} else if (typeof color == 'function') {
			fill = marbleRaceColorUtility.colorToRGBStr(color(this.time))
			stroke = fill
		} else if (typeof color == 'object') {
			if (color.lineWidth)
				lineWidth = color.lineWidth

			if (color.fill) {
				fill = color.fill
				if (typeof fill == 'object')
					fill = marbleRaceColorUtility.colorToRGBStr(fill)
				if (typeof fill == 'function')
					fill = marbleRaceColorUtility.colorToRGBStr(fill(this.time))
			}

			stroke = fill
			if (color.stroke) {
				stroke = color.stroke
				if (typeof stroke == 'object')
					stroke = marbleRaceColorUtility.colorToRGBStr(stroke)
				if (typeof stroke == 'function')
					stroke = marbleRaceColorUtility.colorToRGBStr(stroke(this.time))
			}

			if (color.sprite) {
				sprite = color.sprite
				if (color.spriteSize && size)
					spriteScale = size / color.spriteSize
				else if (color.spriteScale)
					spriteScale = color.spriteScale
				else
					spriteScale = 1
			}
		}

		if (fill[0] == '@')
			fill = marbleRaceConfig.theme[fill.substring(1)]
		if (stroke[0] == '@')
			stroke = marbleRaceConfig.theme[stroke.substring(1)]

		return {
			lineWidth: lineWidth,
			fill: fill,
			stroke: stroke,
			sprite: sprite,
			spriteScale: spriteScale
		}
	},
	randomH: 0,
	randomS: 0,
	randomV: 0,
	updateRandom: function () {
		this.randomH = 360 * Math.random()
		this.randomS = 70 * Math.random() + 30
		this.randomV = 70 * Math.random() + 30
	},
	init: function () {
		this.updateRandom()
		setInterval(function () { marbleRaceColorUtility.time += marbleRaceConfig.renderTimeDelta * .001 }, marbleRaceConfig.renderTimeDelta);
		setInterval(function () { marbleRaceColorUtility.updateRandom() }, 1000);
	},
	f: t => 360 * (t - Math.floor(t)),
	g: t => 50 * (1 + Math.sin(8 * t)),
	g1: t => 40 + 20 * Math.sin(8 * t),
	g2: function (t) {
		let phase = (4 * t / Math.PI) % 3
		return ((phase < 1) ? 25 : 50) * (1 - Math.cos(phase * (Math.PI + Math.PI)))
	},
	g3: function (t) {
		let phase = (4 * t / Math.PI) % 3
		return ((1 <= phase && phase < 2) ? 25 : 50) * (1 - Math.cos(phase * (Math.PI + Math.PI)))
	},
	blink2: function (rgbstr1, rgbstr2, freq) {
		let rgb1 = this.RGBStrToRGB255(rgbstr1)
		let r1 = rgb1[0]
		let g1 = rgb1[1]
		let b1 = rgb1[2]
		let rgb2 = this.RGBStrToRGB255(rgbstr2)
		let r2 = rgb2[0]
		let g2 = rgb2[1]
		let b2 = rgb2[2]
		return function (t) {
			let phase = Math.sin(freq * t)
			let lambda1 = .5 * (1 - phase)
			let lambda2 = .5 * (1 + phase)
			let r = Math.floor(r1 * lambda1 + r2 * lambda2)
			if (r < 0) r = 0
			if (r > 255) r = 255
			let g = Math.floor(g1 * lambda1 + g2 * lambda2)
			if (g < 0) g = 0
			if (g > 255) g = 255
			let b = Math.floor(b1 * lambda1 + b2 * lambda2)
			if (b < 0) b = 0
			if (b > 255) b = 255
			return {
				mode: 'RGB255',
				0: r,
				1: g,
				2: b
			}
		}
	}
}
marbleRaceColorUtility.init()